//
// Created by root on 5/30/17.
//

#include <memory.h>
#include "../basic/operator.h"

Cell* real_add(Scheme *sc, Cell *x, Cell *y) {
    TYPE_CHECK_FOR_CELL(sc, x, REAL_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, REAL_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (strcmp(x1->_type, INTEGER_TYPE) == 0) {
        return int_add(sc, x1, y1);
    } else if (strcmp(x1->_type, FRACTION_TYPE) == 0) {
        return fraction_add(sc, x1, y1);
    } else if (strcmp(x1->_type, DECIMAL_TYPE) == 0) {
        return decimal_add(sc, x1, y1);
    } else {
        return E_NOT_A_REAL;
    }
}

Cell* real_mul(Scheme *sc, Cell *x, Cell *y) {
    TYPE_CHECK_FOR_CELL(sc, x, REAL_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, REAL_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (strcmp(x1->_type, INTEGER_TYPE) == 0) {
        return int_mul(sc, x1, y1);
    } else if (strcmp(x1->_type, FRACTION_TYPE) == 0) {
        return fraction_mul(sc, x1, y1);
    } else if (strcmp(x1->_type, DECIMAL_TYPE) == 0) {
        return decimal_mul(sc, x1, y1);
    } else {
        return E_NOT_A_REAL;
    }
}

Cell* real_sub(Scheme *sc, Cell *x, Cell *y) {
    TYPE_CHECK_FOR_CELL(sc, x, REAL_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, REAL_TYPE);
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (strcmp(x1->_type, INTEGER_TYPE) == 0) {
        return int_sub(sc, x1, y1);
    } else if (strcmp(x1->_type, FRACTION_TYPE) == 0) {
        return fraction_sub(sc, x1, y1);
    } else if (strcmp(x1->_type, DECIMAL_TYPE) == 0) {
        return decimal_sub(sc, x1, y1);
    } else {
        return E_NOT_A_REAL;
    }
}

Cell* real_div(Scheme *sc, Cell *x, Cell *y) {
    TYPE_CHECK_FOR_CELL(sc, x, REAL_TYPE);
    TYPE_CHECK_FOR_CELL(sc, y, REAL_TYPE);
    if (is_zero_number(y)) {
        return E_DIVISION_BY_ZERO;
    }
    Cell* c = combine_convert(sc, x, y);
    Cell* x1 = car(c);
    Cell* y1 = cdr(c);
    if (strcmp(x1->_type, INTEGER_TYPE) == 0) {
        return int_div(sc, x1, y1);
    } else if (strcmp(x1->_type, FRACTION_TYPE) == 0) {
        return fraction_div(sc, x1, y1);
    } else if (strcmp(x1->_type, DECIMAL_TYPE) == 0) {
        return decimal_div(sc, x1, y1);
    } else {
        return E_NOT_A_REAL;
    }
}

double real_to_decimal(Cell *x) {
    if (strcmp(x->_type, INTEGER_TYPE) == 0) {
        return (double)(x->_longValue);
    } else if (strcmp(x->_type, FRACTION_TYPE) == 0) {
        return (double)(car(x)->_longValue / cdr(x)->_longValue);
    } else if (strcmp(x->_type, DECIMAL_TYPE) == 0) {
        return x->_doubleValue;
    } else {
        return 0.0; //unreachable
    }
}

/**
 * not exactly compare
 * */
int numlt(Cell *a, Cell *b) {
    double va = real_to_decimal(a);
    double vb = real_to_decimal(b);
    return (va - vb < -DECIMAL_MIN);
}

int numgt(Cell *a, Cell *b) {
    double va = real_to_decimal(a);
    double vb = real_to_decimal(b);
    return (va - vb > DECIMAL_MIN);
}

int numle(Cell *a, Cell *b) {
    double va = real_to_decimal(a);
    double vb = real_to_decimal(b);
    return (va - vb <= -DECIMAL_MIN);
}

int numge(Cell *a, Cell *b) {
    double va = real_to_decimal(a);
    double vb = real_to_decimal(b);
    return (va - vb >= DECIMAL_MIN);
}

// (< x y ...)
Cell* op_numlt(Scheme *sc) {
    Cell* x = sc->args;
    Cell* value = car(x);
    TYPE_CHECK(sc, x, REAL_TYPE);
    for (x = cdr(x); x != &g_nil; x = cdr(x)) {
        TYPE_CHECK(sc, car(x), REAL_TYPE);
        if (numlt(value, car(x))) {
            sc->returnValue = &g_false;
            return pop_callstack(sc);
        }
        value = car(x);
    }
    sc->returnValue = &g_true;
    return pop_callstack(sc);
}

// (> x y ...)
Cell* op_numgt(Scheme *sc) {
    Cell* x = sc->args;
    Cell* value = car(x);
    TYPE_CHECK(sc, x, REAL_TYPE);
    for (x = cdr(x); x != &g_nil; x = cdr(x)) {
        TYPE_CHECK(sc, car(x), REAL_TYPE);
        if (numgt(value, car(x))) {
            sc->returnValue = &g_false;
            return pop_callstack(sc);
        }
        value = car(x);
    }
    sc->returnValue = &g_true;
    return pop_callstack(sc);
}

// (<= x y ...)
Cell* op_numle(Scheme *sc) {
    Cell* x = sc->args;
    Cell* value = car(x);
    TYPE_CHECK(sc, x, REAL_TYPE);
    for (x = cdr(x); x != &g_nil; x = cdr(x)) {
        TYPE_CHECK(sc, car(x), REAL_TYPE);
        if (numle(value, car(x))) {
            sc->returnValue = &g_false;
            return pop_callstack(sc);
        }
        value = car(x);
    }
    sc->returnValue = &g_true;
    return pop_callstack(sc);
}

// (>= x y ...)
Cell* op_numge(Scheme *sc) {
    Cell* x = sc->args;
    Cell* value = car(x);
    TYPE_CHECK(sc, x, REAL_TYPE);
    for (x = cdr(x); x != &g_nil; x = cdr(x)) {
        TYPE_CHECK(sc, car(x), REAL_TYPE);
        if (numge(value, car(x))) {
            sc->returnValue = &g_false;
            return pop_callstack(sc);
        }
        value = car(x);
    }
    sc->returnValue = &g_true;
    return pop_callstack(sc);
}

Cell* real_to_complex(Scheme *sc, Cell* x) {
    TYPE_CHECK_FOR_CELL(sc, x, REAL_TYPE);
    return make_complex(sc, x, make_integer(sc, 0));
}

void init_real_operators(Scheme *sc) {
    ADD_OPERATOR(sc, "<", numlt);
    ADD_OPERATOR(sc, ">", numgt);
    ADD_OPERATOR(sc, "<=", numle);
    ADD_OPERATOR(sc, ">=", numge);
}
